from enum import Enum, unique
from typing import Optional

from loguru import logger as log

from apps.pdd.pdd.utils.app.core.app_auto_login import AppAutoLogin
from utils.redis_lock import RedisLock
from utils.user_pool.base_user_pool import *


class PDDAppUser(GuestUser):
    """
    PDD的app账号
    """

    redisdb: RedisDB = None
    redis_key: str = None

    def __init__(
        self,
        max_use_times=300,
        use_interval=60 * 60,
        work_time=(6, 23),
        exclusive_time=None,
        **kwargs,
    ):
        """
        @param max_use_times:
        @param use_interval: 使用时间间隔。 支持元组 指定间隔的时间范围 如（5，10）即5到10秒；或直接传整数
        @param work_time: 工作时间，默认 7点到23点
        @param exclusive_time: 独占时长
        """
        self.headers = kwargs["headers"]
        self.proxies = kwargs["proxies"]
        self.cookies = kwargs["cookies"]
        self.user_id = kwargs.get("mobile") or kwargs.get("user_id")
        self.username = kwargs["cookies"]["pdd_user_id"]
        self.max_use_times = max_use_times
        self.use_interval = use_interval
        self.work_time = work_time
        self.exclusive_time = exclusive_time or (
            use_interval[-1] * 5 if isinstance(use_interval, (tuple, list)) else use_interval * 5
        )

        self._delay_use = kwargs.get("_delay_use", 0)  # 延时使用，用于等待解封的用户
        self._login_time = kwargs.get("_login_time", 0)
        self._use_times = kwargs.get("_use_times", 0)
        self._last_use_time = kwargs.get("_last_use_time", 0)
        self._last_refresh_time = kwargs.get("_last_refresh_time", 0)
        self._used_for_spider_name = kwargs.get("_used_for_spider_name")
        self._reset_use_times_date = kwargs.get("_reset_use_times_date")

    def __eq__(self, other):
        return self.username == other.username

    def update(self, ohter):
        self.__dict__.update(ohter.to_dict())

    def sycn_to_redis(self):
        self.redisdb.hset(self.redis_key, self.user_id, self.to_dict())

    def set_delay_use(self, seconds):
        self._delay_use = seconds
        self.sycn_to_redis()

    def set_cookies(self, cookies):
        self.cookies = cookies
        self.sycn_to_redis()

    def delete(self):
        self.redisdb.hdel(self.redis_key, self.user_id)

    def get_last_use_time(self):
        return self._last_use_time

    def get_last_refresh_time(self):
        return self._last_refresh_time

    def get_used_for_spider_name(self):
        return self._used_for_spider_name

    def set_used_for_spider_name(self, name):
        self._used_for_spider_name = name
        self._use_times += 1
        self._last_use_time = time.time()
        self.sycn_to_redis()

    def set_refreshed_time(self):
        self._last_refresh_time = time.time()
        self.sycn_to_redis()

    def is_time_to_use(self):
        if self._delay_use:
            is_time = time.time() - self._last_use_time > self._delay_use
            if is_time:
                self._delay_use = 0  # 不用同步了，使用用户时会同步

        else:
            is_time = time.time() - self._last_use_time > (
                random.randint(*self.use_interval)
                if isinstance(self.use_interval, (tuple, list))
                else self.use_interval
            )

        return is_time

    def is_time_to_refresh(self):
        is_time = time.time() - self._last_refresh_time > 12 * 60 * 60  # 每12小时刷新token
        return is_time

    def reset_use_times(self):
        self._use_times = 0
        self._reset_use_times_date = datetime.now().strftime("%Y-%m-%d")
        self.sycn_to_redis()

    @property
    def use_times(self):
        current_date = datetime.now().strftime("%Y-%m-%d")
        if current_date != self._reset_use_times_date:
            self.reset_use_times()

        return self._use_times

    def is_overwork(self):
        if self.use_times > self.max_use_times:
            log.info("账号 {} 请求次数超限制".format(self.username))
            return True

        return False

    def is_at_work_time(self):
        if datetime.now().hour in list(range(*self.work_time)):
            return True

        log.info("账号 {} 不再工作时间内".format(self.username))
        return False

    def refresh(self):
        cls = AppAutoLogin(cookie=self.cookies, headers=self.headers)
        pdd_access_token = cls.refresh_token()
        if pdd_access_token:
            self.proxies = cls.session.proxies
            self.cookies = {**self.cookies, "PDDAccessToken": pdd_access_token}
            log.info(f"刷新用户成功 {self.user_id} {self.username} {pdd_access_token}")
        else:
            log.error(f"刷新用户失败 {self.user_id} {self.username} {self.cookies} {self.headers}")
            self.delete()


@unique
class GoldUserStatus(Enum):
    # 使用状态
    USED = "used"
    SUCCESS = "success"
    OVERDUE = "overdue"  # cookie 过期
    SLEEP = "sleep"
    EXCEPTION = "exception"
    # 登陆状态
    LOGIN_SUCCESS = "login_success"
    LOGIN_FALIED = "login_failed"


class PDDAppUserPool(UserPoolInterface):
    """
    账号昂贵、限制查询次数的用户的UserPool
    """

    def __init__(
        self,
        redis_key,
        max_users_num=10,
        *,
        keep_alive=False,
        auto_delete=True,
    ):
        """
        @param redis_key: user存放在redis中的key前缀
        @param users: 账号信息
        @param keep_alive: 是否保持常驻，以便user不足时立即补充
        """
        self._tab_user_pool = "{redis_key}:h_{user_type}_pool".format(redis_key=redis_key, user_type="gold")

        self.users = []
        self._keep_alive = keep_alive
        self._auto_delete = auto_delete
        self.max_users_num = max_users_num

        self._redisdb = RedisDB()
        self._users_id = []

        # # 给user的类属性复制
        PDDAppUser.redisdb = self._redisdb
        PDDAppUser.redis_key = self._tab_user_pool

        self.__sync_users_info()

    def __sync_users_base_info(self):
        # 本地同步基本信息到redis, 注 只能在初始化函数内同步
        self._users_id = self._redisdb.hkeys(self._tab_user_pool)
        for user_id in self._users_id:
            cache_user = self.get_user_by_id(user_id)
            if cache_user not in self.users:
                self.users.append(cache_user)

    def __sync_users_info(self):
        # redis同步登录信息到本地
        self.__sync_users_base_info()
        self_user_back = []
        user_id_set = set()
        for user in self.users:
            if user.user_id not in user_id_set:
                cache_user = self.get_user_by_id(user.user_id)
                if cache_user:
                    self_user_back.append(cache_user)
            else:
                user_id_set.add(user.user_id)
                continue
        self.users = self_user_back

    def _load_users_id(self):
        self._users_id = self._redisdb.hkeys(self._tab_user_pool)
        if self._users_id:
            random.shuffle(self._users_id)

    def _get_user_id(self):
        if not self._users_id:
            self._load_users_id()

        if self._users_id:
            return self._users_id.pop()

    def login(self, platform="dy", **kwargs):
        try:
            response = AppAutoLogin(platform=platform).run_auto(not_mobile_list=self._users_id, **kwargs)
            user = PDDAppUser(_last_refresh_time=time.time(), **response)
            self.add_user(user)
            if user:
                self.users.append(user)
            return user
        except Exception as e:
            log.exception(f"登录失败 {e}")

    def get_user_by_id(self, user_id: str) -> PDDAppUser:
        user_str = self._redisdb.hget(self._tab_user_pool, user_id)
        if user_str:
            user = PDDAppUser(**eval(user_str))
            return user

    def get_user(
        self,
        block=True,
        username=None,
        used_for_spider_name=None,
        not_limit_use_interval=False,
    ) -> Optional[PDDAppUser]:
        """
        @params username: 获取指定的用户
        @params used_for_spider_name: 独享式使用，独享爬虫的名字。其他爬虫不可抢占
        @params block: 无用户时是否等待
        @params not_limit_frequence: 不限制使用频率
        @return: PDDAppUser
        """
        while True:
            try:
                user_id = username or self._get_user_id()
                user_str = None
                if user_id:
                    user_str = self._redisdb.hget(self._tab_user_pool, user_id)

                if (not user_id or not user_str) and block:
                    self._keep_alive = False
                    self.run(username)
                    continue

                # 取到用户
                user = PDDAppUser(**eval(user_str))

                # 独占式使用，若为其他爬虫，检查等待使用时间是否超过独占时间，若超过则可以使用
                if user.get_used_for_spider_name() and user.get_used_for_spider_name() != used_for_spider_name:
                    wait_time = time.time() - user.get_last_use_time()
                    if wait_time < user.exclusive_time:
                        log.info(
                            "用户{} 被 {} 爬虫独占，需等待 {} 秒后才可使用".format(
                                user.username,
                                user.get_used_for_spider_name(),
                                user.exclusive_time - wait_time,
                            )
                        )
                        time.sleep(1)
                        continue

                if not user.is_overwork() and user.is_at_work_time():

                    if not_limit_use_interval or user.is_time_to_use():
                        user.set_used_for_spider_name(used_for_spider_name)
                        log.debug("使用用户 {} {}".format(user.username, user.user_id))
                        self.record_user_status(user.user_id, GoldUserStatus.USED)
                        return user
                    else:
                        log.debug("{} {} 用户使用间隔过短 查看下一个用户".format(user.username, user.user_id))
                        time.sleep(1)
                        continue
                else:
                    if not user.is_at_work_time():
                        log.info("用户 {} 不在工作时间 sleep 60s".format(user.username))
                        if block:
                            time.sleep(60)
                            continue
                        else:
                            return None

            except Exception as e:
                log.exception(e)
                time.sleep(1)

    def del_user(self, user_id: str):
        user = self.get_user_by_id(user_id)
        if user:
            user.set_cookies(None)
            user.delete()
            self.record_user_status(user.user_id, GoldUserStatus.OVERDUE)

    def add_user(self, user: PDDAppUser):
        user.sycn_to_redis()

    def delay_use(self, user_id: str, delay_seconds: int):
        user = self.get_user_by_id(user_id)
        if user:
            user.set_delay_use(delay_seconds)

        self.record_user_status(user_id, GoldUserStatus.SLEEP)

    def record_success_user(self, user_id: str):
        self.record_user_status(user_id, GoldUserStatus.SUCCESS)

    def record_exception_user(self, user_id: str):
        self.record_user_status(user_id, GoldUserStatus.EXCEPTION)

    def run(self, username=None):
        log.info("当前在线user数为 {}".format(len(self.users)))
        while True:
            try:
                with RedisLock(key=self._tab_user_pool, lock_timeout=3600, wait_timeout=0) as _lock:
                    if _lock.locked:
                        self.__sync_users_info()

                        if len(self.users) < self.max_users_num:
                            self.login()
                        log.info("当前在线user数为 {}".format(len(self.users)))

                        if self._auto_delete:
                            for user in self.users:
                                if user.is_time_to_refresh():
                                    user.refresh()
                                    if self.get_user_by_id(user.user_id):
                                        user.set_refreshed_time()

                            self.__sync_users_info()

                if self._keep_alive:
                    time.sleep(10)
                else:
                    break

            except Exception as e:
                log.exception(e)
                time.sleep(1)

    def record_user_status(self, user_id: str, status: GoldUserStatus):
        # metrics.emit_counter(user_id, 1, classify=f"users_{status.value}")
        ...


if __name__ == "__main__":
    test = PDDAppUserPool(redis_key="user:pdd:app", max_users_num=13, keep_alive=True)
    test.run()
    """
    /data/data/com.xunmeng.pinduoduo/files/pinUserFile
    {"access_token":"RNVGVBSHCDYA32XDRWIVX6YXHFKOIXYCKQ5VI2HVNGL44ILPSVKA1238122","uid":"3364102153089","uin":"4G6X3F4AIRLJMEADPXOZRNQHYU_GEXDA"}
    {"access_token":"6IMRBVPPB3PGVQAFWPIHYAA4QVW5YMTFV5D2OR4UK2FG52DNXVWA120ed54","uid":"2323580273901","uin":"QCCSF2LZ6XPGYQRIOCVIHM3KDE_GEXDA"}
    {"access_token":"RVJ4URY2QAJ2RWD3EZALB7CDX5N4D4D3FVUWQT4LOLETZ6VKA37Q123dc69","uid":"2602841442268","uin":"67ZPJR3ZNHAD3LSZKEMKPMU5KM_GEXDA"}
    16584366365
    """
